package cn.edu.sjtu.stap.cg.builder;

import java.util.*;

import org.aspectj.asm.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.InvocationSite;

import cn.edu.sjtu.stap.cg.*;
import cn.edu.sjtu.stap.graph.*;
import cn.edu.sjtu.stap.hg.*;
import cn.edu.sjtu.stap.util.*;

public class AjCgBuilder {
    private Map<AbstractMethodDeclaration, CallGraph> currentCgs = null;

    public AjCgBuilder() {
        // Empty.
    }

    public void initialize() {
        // Empty.
    }

    public void buildAjCallGraph(AsmManager manager, Map<AbstractMethodDeclaration, CallGraph> cgs) {
        AstAsmMap astAsmMap = AstAsmMap.getInstance();
        IRelationshipMap relationshipMap = manager.getRelationshipMap();
        IHierarchy hierarchy = manager.getHierarchy();

        currentCgs = cgs;

        // Map Calls to ProgramElement.
        makeCallMappingOnAstAsmMap(astAsmMap, CallMap.getInstance(), hierarchy);

        Iterator<IProgramElement> peIt = findShadows(hierarchy).iterator();
        while (peIt.hasNext()) {
            IProgramElement methodPe = peIt.next();

            // Get the ASTNode of this shadow.
            ASTNode shadow = astAsmMap.get(methodPe);

            // Get the relationship between the methods and advices.
            List/* <IRelationship> */relationships = relationshipMap.get(methodPe);
            if (null == relationships) {
                continue;
            }

            Iterator/* <IRelationship> */rIt = relationships.iterator();
            while (rIt.hasNext()) {
                IRelationship r = (IRelationship) rIt.next();
                List/* <String> */targets = r.getTargets();
                if (null == targets) {
                    continue;
                }

                Iterator/* <String> */tIt = targets.iterator();

                while (tIt.hasNext()) {
                    String s = (String) tIt.next();
                    IProgramElement a = hierarchy.findElementForHandle(s);
                    if (a.getKind() == IProgramElement.Kind.ADVICE) {
                        ASTNode advice = astAsmMap.get(a);
                        addAdvised(shadow, advice);
                    }
                }
            }
        }
    }

    private void makeCallMappingOnAstAsmMap(final AstAsmMap astAsmMap, final CallMap callMap, final IHierarchy hierarchy) {
        hierarchy.getRoot().walk(new HierarchyWalker() {
            protected void preProcess(IProgramElement node) {
                if (node.getKind() == IProgramElement.Kind.CODE) {
                    String s = node.getHandleIdentifier(true);
                    if (null != s && s.contains("call")) {
                        String callKind = null;
                        if (s.contains("method-call")) {
                            callKind = "method-call";
                        } else if (s.contains("constructor-call")) {
                            callKind = "constructor-call";
                        }
                        if (callKind == null) {
                            return;
                        }
                        String callerName = s.substring(0, s.indexOf(callKind) - 1);
                        String calleeName = s
                                .substring(s.indexOf(callKind) + callKind.length() + 1, s.lastIndexOf(')'));

                        IProgramElement callerPe = hierarchy.findElementForHandle(callerName);
                        ASTNode callerNode = astAsmMap.get(callerPe);

                        if (callerNode instanceof AbstractMethodDeclaration) {
                            AbstractMethodDeclaration method = (AbstractMethodDeclaration) callerNode;
                            List<ASTNode> calls = callMap.getCalls(method);
                            if (calls != null) {
                                Iterator<ASTNode> i = calls.iterator();
                                while (i.hasNext()) {
                                    ASTNode call = i.next();
                                    if (checkCall(call, calleeName)) {
                                        astAsmMap.addMapping(node, call);
                                    }
                                }
                            }
                        }

                    }
                }
            }
        });
    }

    private boolean checkCall(ASTNode call, String calleeName) {
        AbstractMethodDeclaration callee = HierarchyGraph.getStaticCallee(call);

        if (null != callee) {
            String s = currentCgs.get(callee).getRoot().getMethodName();
            if (s.equals(calleeName)) {
                return true;
            }
        }

        return false;
    }

    private List<IProgramElement> findShadows(IHierarchy hierarchy) {
        final List<IProgramElement> shadows = new ArrayList<IProgramElement>();

        hierarchy.getRoot().walk(new HierarchyWalker() {
            protected void preProcess(IProgramElement node) {
                IProgramElement.Kind kind = node.getKind();
                if (kind == IProgramElement.Kind.METHOD || kind == IProgramElement.Kind.CONSTRUCTOR
                        || kind == IProgramElement.Kind.ADVICE || kind == IProgramElement.Kind.INTER_TYPE_METHOD) {
                    shadows.add(node);
                } else if (kind == IProgramElement.Kind.CODE) {
                    String s = node.getHandleIdentifier(true);
                    if (null != s && s.contains("call")) {
                        shadows.add(node);
                    }
                }
            }
        });

        return shadows;
    }

    private void addAdvised(ASTNode shadow, ASTNode advice) {
        if (currentCgs == null) {
            throw new NullPointerException("Invalid Call Graphs!");
        }

        if (shadow == null || advice == null) {
            return;
        }

        if (shadow instanceof AbstractMethodDeclaration) {
            CallGraph cg = currentCgs.get(shadow);
            if (null != cg) {
                CgMethod advisedNode = cg.getRoot();
                CgAdvice adviceNode = (CgAdvice) currentCgs.get(advice).getRoot();
                addAdvised(advisedNode, adviceNode, cg);
            }
        } else if (shadow instanceof InvocationSite) {
            AbstractMethodDeclaration caller = CallMap.getInstance().findCaller(shadow);
            CallGraph cg = currentCgs.get(caller);
            if (null != cg) {
                AbstractMethodDeclaration c = HierarchyGraph.getStaticCallee(shadow);
                List<CgMethod> callees = null;
                try {
                    callees = cg.findOverridingCallees(caller, c);
                } catch (MethodNotFoundException e) {
                    System.err.println("Shadow does not exist!");
                    e.printStackTrace();
                    return;
                }
                Iterator<CgMethod> cIt = callees.iterator();
                while (cIt.hasNext()) {
                    AbstractMethodDeclaration callee = (AbstractMethodDeclaration) cIt.next().getASTNode();
                    CgCallSite advisedNode = cg.find(caller, callee);
                    CgAdvice adviceNode = (CgAdvice) currentCgs.get(advice).getRoot();
                    addAdvised(advisedNode, adviceNode, cg);
                }
            }
        }
    }

    private void addAdvised(CgNode shadow, CgAdvice advice, CallGraph cg) {
        Iterator<GraphNode> i = shadow.getDestinationNodeSet().iterator();
        while (i.hasNext()) {
            if (i.next() == advice) {
                return;
            }
        }

        CgAdvised e = new CgAdvised();
        e.setSourceNode(shadow);
        e.setDestinationNode(advice);
        cg.add(e);
    }
}
